#ifndef ADC_DL642A_h
#define ADC_DL642A_h

#include <sys/types.h>
#include <stdio.h>

extern "C" {
#include "sis3100_vme_calls.h"
#include "sis3100_dma.h"
}

#include <string>
#include <sstream>
#include "VmeModule.h"
#include "IOError.h"

using namespace std;

// Instances of class ADC_DL642A represent a single vme card (base card DL600 )
// with three DL642A Gated Integrators (in the following called ADCs)
// piggy-back modules and one DL636 Control Module (also piggy-back)
// plugged on it.

// NOTICE: the DL642A modules have been modified by A.Rausch and now require
//         32-bit-data vme requests (A16D32). This allows to use A16D32DMAFIFO
//         accesses which are much faster than single A16D16 requests.
//         internally the DL642 Gated Integrator uses 14-Bit values, so the
//         rest is garbage
//      -> the method getFIFO(no) already does the job and returns valid data.

class ADC_DL642A : public VmeModule
{
 public:
  const static int ENABLE_OFFSET     = 0x00;
  const static int HITCNT_LO_OFFSET  = 0x02;
  const static int HITCNT_HI_OFFSET  = 0x04;
  const static int STARTSEQ_OFFSET   = 0x04;
  const static int START_OFFSET      = 0x06;
  const static int PULSE_OFFSET      = 0x08;
  const static int END_OFFSET        = 0x0a;
  const static int CLEARFIFO_OFFSET  = 0x0c;
  const static int FIFO_OFFSET       = 0x20; //fifo0=FIFO_OFFSET
                                             //fifo1=FIFO_OFFSET + FIFO_DELTA
  const static int FIFO_DELTA        = 0x20; //fifo2=FIFO_OFFSET + 2*FIFO_DELTA
  const static int STATUS_OFFSET     = 0x80;
  const static int INTERRUPT_OFFSET  = 0x82;

  ADC_DL642A( int handle, unsigned int address ) : VmeModule(handle,address) {}

  bool isEnabled() throw(IOError)
  {
    u_int16_t e;
    int r;

    if( (r=vme_A16D16_read(handle,address+ENABLE_OFFSET,&e)) != 0 ) {
				stringstream ss;

				ss << hex
					 << "ADC_DL642A::isEnabled():\n"
					 << "  vme_A16D16_read() returned non zero\n"
					 << "  handle=0x" << handle
					 << "  address=0x" << address+ENABLE_OFFSET
					 << "  data_address=0x" << (u_int32_t)&e
					 << dec;

      throw IOError(ss.str().c_str(),r);
		}

    //  D0 = 0 --> Disable    D0 = 1 --> Enable
    return (e & 0x1);
  }

  void setEnabled(bool b) throw(IOError)
  {
    u_int16_t e = (u_int16_t) b;
    int r;

    if( (r=vme_A16D16_write(handle,address+ENABLE_OFFSET,e)) != 0 ) {
				stringstream ss;

				ss << hex
					 << "ADC_DL642A::setEnabled():\n"
					 << "  vme_A16D16_write() returned non zero\n"
					 << "  handle=0x" << handle
					 << "  address=0x" << address+ENABLE_OFFSET
					 << "  data=0x" << e
					 << dec;

      throw IOError(ss.str().c_str(),r);
		}
  }


  /// internally the counter works with more than 16-bit (24??)
  /// so maybe this function has to be re-written
  u_int32_t getHitCounter() throw(IOError)
  {
    u_int16_t hi,lo;
    int r;

    if( (r=vme_A16D16_read(handle,address+HITCNT_LO_OFFSET,&lo)) != 0 ) {
				stringstream ss;

				ss << hex
					 << "ADC_DL642A::getHitCounter(): (reading low-word)\n"
					 << "  vme_A16D16_read() returned non zero\n"
					 << "  handle=0x" << handle
					 << "  address=0x" << address+HITCNT_LO_OFFSET
					 << "  data_address=0x" << (u_int32_t)&lo
					 << dec;

      throw IOError(ss.str().c_str(),r);
		}

    if( (r=vme_A16D16_read(handle,address+HITCNT_HI_OFFSET,&hi)) != 0 ) {
				stringstream ss;

				ss << hex
					 << "ADC_DL642A::getHitCounter(): (reading hi-word)\n"
					 << "  vme_A16D16_read() returned non zero\n"
					 << "  handle=0x" << handle
					 << "  address=0x" << address+HITCNT_HI_OFFSET
					 << "  data_address=0x" << (u_int32_t)&hi
					 << dec;

      throw IOError(ss.str().c_str(),r);
		}

    return ((hi << 16) | lo);    
  }

  void clearHitCounter() throw(IOError)
  {
    int r;

    // the manual say the data written to the hitcounter
    // address doesn't matter, so I chose 0xaffe
    if( (r=vme_A16D16_write(handle,address+HITCNT_LO_OFFSET,0xaffe)) != 0 )
      throw IOError("ADC_DL642A::clearHitCounter():\n"
		    "  vme_A16D16_write() returned non zero",r);
  } 

  void clearFIFO() throw(IOError)
  {
    int r;

    // the manual say the data written to the hitcounter
    // address doesn't matter, so I chose 0xaffe
    if( (r=vme_A16D16_write(handle,address+CLEARFIFO_OFFSET,0xaffe)) != 0 )
      throw IOError("ADC_DL642A::clearFIFO():\n"
		    " vme_A16D16_write() returned non zero",r);    
  }

  /// reads out fifo #n, where n = 0 .. 2
  /// this is the "lame" A16D16 method
  u_int16_t readFIFO(unsigned int n) throw(IOError)
  {
    u_int16_t d;
    int r;

    // to avoid wrong address calculation: throw exception, if not in range 0-2
    if(n>2) throw IOError("ADC_DL642A::readFIFO():\n"
			  "  fifo number out of range",n);

    if( (r=vme_A16D16_read(handle,address+FIFO_OFFSET+n*FIFO_DELTA,&d)) != 0 )
      throw IOError("ADC_DL642A::readFIFO():\n"
		    "  vme_A16D16_read() returned non zero",r);

    if( d & 0xc000 )
      throw IOError("ADC_DL642A::readFIFO():\n  data from adc > 16383",d);

    return d;    
  }

  u_int16_t readFIFO32(unsigned int n) throw(IOError)
  {
    u_int32_t d;
    int r;

    // to avoid wrong address calculation: throw exception, if not in range 0-2
    if(n>2) throw IOError("ADC_DL642A::readFIFO():\n"
			  "  fifo number out of range",n);

    if( (r=vme_A16D32_read(handle,address+FIFO_OFFSET+n*FIFO_DELTA,&d)) != 0 )
      throw IOError("ADC_DL642A::readFIFO():\n"
		    "  vme_A16D16_read() returned non zero",r);

    if( d & 0xc000 )
      throw IOError("ADC_DL642A::readFIFO():\n  data from adc > 16383",d);

    return (u_int16_t)(d);    
  }

  u_int16_t readFIFO32DMA(unsigned int n, unsigned int count,
			  unsigned int *buffer) throw(IOError)
  {
    u_int32_t read;
    int r;

    // to avoid wrong address calculation: throw exception, if not in range 0-2
    if(n>2) throw IOError("ADC_DL642A::readFIFO32DMA():\n"
			  "  fifo number out of range",n);

    if( (r=vme_A16DMA_D32FIFO_read(handle,address+FIFO_OFFSET+n*FIFO_DELTA,
																	 buffer,count,&read)) != 0 ) {
				stringstream ss;

				ss << hex
					 << "ADC_DL642A::readFIFO32DMA():\n"
					 << "  vme_A16DMA_D32FIFO_read() returned non zero\n"
					 << "    parameters: handle=0x" << handle
					 << " vme_address=0x" << (address+FIFO_OFFSET+n*FIFO_DELTA)
					 << " buffer_adress=0x" << (u_int32_t)buffer
					 << " count=0x" << count
					 << " read=0x" << read
					 << dec;
				
				throw IOError(ss.str().c_str(),r);
		}

    if( read != count ) {
				std::stringstream ss;
				
				ss << "ADC_DL642A::readFIFO32DMA():\nrequested " << count
					 << " words, vme_A16DMA_D32FIFO_read() returned " << read
					 << " words";
				
				throw IOError(ss.str().c_str(),read);
    }

    return (u_int16_t)(read);
  }

  u_int16_t getStatus() throw(IOError)
  {
    u_int16_t s;
    int r;

    if( (r=vme_A16D16_read(handle,address+STATUS_OFFSET,&s)) != 0 )
      throw IOError("ADC_DL642A::getStatus():\n"
		    "  vme_A16D16_read() returned non zero",r);    

    return s;
  }

  char* getStatusString() throw(IOError)
  {
    u_int16_t s;
    int r;
    static char sstr[1024];

    if( (r=vme_A16D16_read(handle,address+STATUS_OFFSET,&s)) != 0 )
      throw IOError("ADC_DL642A::getStatus():\n"
		    "  vme_A16D16_read() returned non zero",r);    

    sprintf(sstr,
	    "FIFO 0: full=%d empty=%d\n"
	    "FIFO 1: full=%d empty=%d\n"
	    "FIFO 2: full=%d empty=%d\n",
	    !((s & 0x0004) >> 2), !((s & 0x0008) >> 3),
	    !((s & 0x0010) >> 4), !((s & 0x0020) >> 5),
	    !((s & 0x0040) >> 6), !((s & 0x0080) >> 7)
	   );

    return sstr;
  }

  /// all module reset
  void reset() throw(IOError)
  {
    int r;

    // the manual does not say what to write in this case
    // so as usal I chose 0xaffe
    if( (r=vme_A16D16_write(handle,address+STATUS_OFFSET,0xaffe)) != 0 ) {
	stringstream ss;
	ss << "ADC_DL642A::reset():\n"
	   << "  vme_A16D16_write() returned non zero\n"
	   << "  adc@0x" << hex << address << endl; 
	throw IOError(ss.str().c_str(),r);    
    }
  }

  ///
  u_int16_t getInterruptRegister() throw(IOError)
  {
    u_int16_t s;
    int r;

    if( (r=vme_A16D16_read(handle,address+INTERRUPT_OFFSET,&s)) != 0 )
      throw IOError("ADC_DL642A::getInterruptRegister():\n"
		    "  vme_A16D16_read() returned non zero",r);

    return s;
  }

  void setInterruptRegister( u_int16_t d ) throw(IOError)
  {
    int r;

    if( (r=vme_A16D16_write(handle,address+INTERRUPT_OFFSET,d)) != 0 )
      throw IOError("ADC_DL642A::setInterruptRegister():\n"
		    "  vme_A16D16_write() returned non zero",r); 
  }

  void setTestSeq( u_int16_t start, u_int16_t pulse, u_int16_t end )
      throw(IOError)
  {
    int r;

    if( (start < 2) || (start > 63) )
      throw IOError("ADC_DL642A::setTestSeq():\n"
		    "  start must be in range 2..63", start); 

    if( (pulse < 2) || (pulse > 63) )
      throw IOError("ADC_DL642A::setTestSeq():\n"
		    "  pulse must be in range 2..63", pulse); 

    if( (end < 2) || (end > 63) )
      throw IOError("ADC_DL642A::setTestSeq():\n"
		    "  end must be in range 2..63", end); 

    if( (r=vme_A16D16_write(handle,address+START_OFFSET,start)) != 0 )
      throw IOError("ADC_DL642A::setTestSeq():\n"
		    "  vme_A16D16_write() returned non zero",r); 

    if( (r=vme_A16D16_write(handle,address+PULSE_OFFSET,pulse)) != 0 )
      throw IOError("ADC_DL642A::setTestSeq():\n"
		    "  vme_A16D16_write() returned non zero",r); 

    if( (r=vme_A16D16_write(handle,address+END_OFFSET,end)) != 0 )
      throw IOError("ADC_DL642A::setTestSeq():\n"
		    "  vme_A16D16_write() returned non zero",r); 
  }

  void startTestSeq() throw(IOError)
  {
    int r;

    //doesn't matter what you write, so I chose 0xaffe
    if( (r=vme_A16D16_write(handle,address+STARTSEQ_OFFSET,0xaffe)) != 0 )
      throw IOError("ADC_DL642A::setTestSeq():\n"
		    "  vme_A16D16_write() returned non zero",r);     
  }
};

#endif
